﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Red.GameData
{
    public partial class EditorPropertyView
    {
        /// <summary>
        /// Resolve final (last) value for this property.
        /// Please study this function carefully so you know how this works in details
        /// </summary>
        /// <returns></returns>
        private Raw.DataValue ResolveSingleValue()
        {
            // the value to return
            Raw.DataValue returnValue = null;

            // walk the object stack until we find object with value
            var stack = this.Object.RawDataStack;
            foreach (var obj in stack)
            {
                // get property
                var prop = obj.GetPropertyNoCreate(ReflectionProp.Name);
                if (prop != null)
                {
                    var values = prop.Values;
                    if (values.Count > 0)
                    {
                        // return the last value
                        returnValue = values[values.Count - 1];
                    }
                }
            }

            // return final resolved value
            return returnValue;
        }

        /// <summary>
        /// Resolve list of final values for this property
        /// NOTE: suppressed values are NOT extractable
        /// Please study this function carefully so you know how this works in details
        /// </summary>
        /// <returns>List of resolved values</returns>
        private Raw.DataValue[] ResolveAllValues(bool allowSuppressedValues = false)
        {
            return null;
        }

        /// <summary>
        /// Resolve the cached display value if needed
        /// </summary>
        private void EnsureDataConsistency()
        {
            // check cached value
            if ((_ConsistencyToken != null) && (_ConsistencyToken.IsConsistent))
                return;

            // resolve values
            ResolveData();
        }

        /// <summary>
        /// Helper structure capturing a little bit more info about resolved values
        /// </summary>
        private class RawValueInfo
        {
            /// <summary>
            /// The value
            /// </summary>
            public Raw.DataValue        Value;

            /// <summary>
            /// If the value was suppressed here's the information by whom
            /// For a non suppressed values this is NULL
            /// </summary>
            public Raw.DataProperty     SuppressedBy; // may be null

            /// <summary>
            /// If the value was replaced here's the information by whom
            /// Note that only the last replacer is tracked
            /// </summary>
            public Raw.DataProperty     ReplacedBy; // may be null
        }

        /// <summary>
        ///  Cached node values
        /// </summary>
        private RawValueInfo[] _CachedRawValues;

        /// <summary>
        /// Resolve values for the node - can use cached values if possible
        /// </summary>
        /// <returns></returns>
        private bool UpdateRawValues(ref RawValueInfo[] outCachedRawValues)
        {
            //Debug.Assert(!IsArrayElement); // this should not be called directly on array elements (it's wasteful)

            // use the cached values if possible
            if ((_ConsistencyToken != null) && _ConsistencyToken.IsConsistent)
            {
                outCachedRawValues = _CachedRawValues;
                return false; // no change
            }

            // reset state
            _CachedIsOverriden = false;
            _CachedIsSuppressed = false;
            _CachedDisplayObject = null;
            _CachedIsDefaultValue = false;
            _CachedValue = "";
            _CachedDisplayValue = "";
            _CachedIsWritable = true;
            _CachedNoValue = true;
            _CachedNumArrayElements = 0;
            _CachedArrayElementGUIDs = null;
            _CachedCompoundGenerationId = 0;

            // output list
            var values = new List<RawValueInfo>();
            var consistencyObjects = new List<Red.Core.DataConsistency.ConsistentObject>();

            // process the object stack
            var stack = Object.RawDataStack;
            bool processIncomingValues = true;
            foreach (var obj in stack)
            {
                // add object to consistency table (our captured state depends on it)
                consistencyObjects.Add(obj);

                // get property
                var prop = obj.GetPropertyNoCreate(ReflectionProp.Name);
                if (prop != null)
                {
                    // add property to consistency table (our captured state depends on it)
                    consistencyObjects.Add(prop);

                    // state must be preserved
                    if (processIncomingValues)
                    {
                        // clear all incoming values
                        if (prop.ClearAllIncomingValues)
                            values.Clear();

                        // suppress values
                        foreach (var suppressed in prop.SupressedValues)
                        {
                            foreach (var val in values)
                            {
                                if (val.Value.ID == suppressed)
                                {
                                    val.SuppressedBy = prop;
                                    break;
                                }
                            }
                        }

                        // is this a property override from OUR object ?
                        if (prop.Values.Count > 0 && (obj == Object.RawData))
                            _CachedIsOverriden = true;

                        // add/replace values
                        foreach (var incomingVal in prop.Values)
                        {
                            bool existing = false;
                            for (int i = 0; i < values.Count; ++i)
                            {
                                if (values[i].Value.ID == incomingVal.ID)
                                {
                                    values[i].SuppressedBy = null; // unsuppress if replaced
                                    values[i].ReplacedBy = prop;
                                    values[i].Value = incomingVal;
                                    existing = true;
                                    break;
                                }
                            }

                            // new 
                            if (!existing)
                            {
                                var info = new RawValueInfo();
                                info.Value = incomingVal;
                                values.Add(info);
                            }
                        }
                    }

                    // final prop - no newer values allowed
                    if (prop.IsFinal)
                    {
                        // don't allow newer values to be added into the list
                        processIncomingValues = false;

                        // if the final flag occurs in base object we need to know that
                        if (prop.Parent != Object.RawData)
                            _CachedIsWritable = false; // property was "finalized" in parent object
                    }
                }
            }

            // update consistency token
            _ConsistencyToken = new Red.Core.DataConsistency.StateToken(consistencyObjects);

            // return captured values
            _CachedRawValues = values.ToArray();
            _CachedNoValue = (values.Count == 0);

            // update the "consistency" value
            _CachedCompoundGenerationId = Red.Core.Crc64.DefaultSeed;
            foreach (var value in values)
            {
                byte[] id = value.Value.ID.ToByteArray();
                _CachedCompoundGenerationId = Red.Core.Crc64Iso.Compute(_CachedCompoundGenerationId, id);
            }

            // cache array specific stuff
            if (IsArray)
            {
                _CachedNumArrayElements = (UInt32)values.Count;
                _CachedArrayElementGUIDs = new Guid[values.Count];

                for (int i = 0; i < values.Count; ++i)
                    _CachedArrayElementGUIDs[i] = values[i].Value.ID;
            }

            // new values composed
            outCachedRawValues = _CachedRawValues;
            return true;
        }

        /// <summary>
        /// Resolve cached value stored in this property
        /// </summary>
        private void ResolveData()
        {
            // convert the array of input values into the actual cached value
            RawValueInfo[] rawValues = null;
            /*var dataUpdated = IsArrayElement ? ParentProp.UpdateRawValues(ref rawValues) : UpdateRawValues( ref rawValues);
            if (!dataUpdated && !IsArrayElement)
                return; // data is up to date*/
            var dataUpdated = UpdateRawValues( ref rawValues);
            if (!dataUpdated)
                return; // data is up to date*/

            // array property, just capture info about element count
            if (IsArray)
            {
                // update the display string for the arrays
                var numValues = rawValues.Length;
                if (IsInlined)
                {
                    if (numValues == 0)
                        _CachedDisplayValue = "<empty inplace array>";
                    else if (numValues == 1)
                        _CachedDisplayValue = "<inplace array of 1 object>";
                    else
                        _CachedDisplayValue = "<inplace array of " + numValues.ToString() + " objects>";
                }
                else
                {
                    if (numValues == 0)
                        _CachedDisplayValue = "<empty array>";
                    else if (numValues == 1)
                        _CachedDisplayValue = "<array of 1 element>";
                    else
                        _CachedDisplayValue = "<array of " + numValues.ToString() + " elements>";
                }
            }
            else
            {
                // extract the final element
                RawValueInfo singleVal = null;
                if (IsArrayElement)
                {
                    // for array elements use the specified item
                    foreach (var val in rawValues)
                    {
                        if (val.Value.ID == ValueID)
                        {
                            singleVal = val;
                            break;
                        }
                    }

                    // for array elements the suppressed and override flags are a little bit different
                    if (singleVal != null)
                    {
                        // this incoming array value was suppressed by a property defined in our parent object
                        _CachedIsSuppressed = ((singleVal.SuppressedBy != null) && (singleVal.SuppressedBy.Parent == Object.RawData));

                        // this incoming array value is defined by our property
                        _CachedIsOverriden = (singleVal.Value.Parent.Parent == Object.RawData);
                    }                    
                }
                else
                {
                    // use the LAST value in the resolve
                    if (rawValues.Length > 0)
                        singleVal = rawValues[rawValues.Length - 1];
                }

                // no value
                if (singleVal == null)
                {
                    // find the default value in the reflection class
                    var defaultValueStr = Object.ReflectionClass.FindDefaultValue(ReflectionProp.Name);
                    if (defaultValueStr != null)
                    {
                        // we have a default value for this prop, use it for DISPLAY ONLY
                        _CachedIsDefaultValue = true;
                        _CachedDisplayValue = defaultValueStr;
                        _CachedValue = defaultValueStr;
                    }
                    else
                    {
                        // no default value whatsoever
                        _CachedIsDefaultValue = false;
                        _CachedDisplayValue = "<no value defined>";
                        _CachedValue = "";
                    }
                }
                else if ( IsObject )
                {
                    // cache the raw text
                    _CachedValue = singleVal.Value.Value;

                    // determine display value for it
                    if (_CachedValue == "")
                    {
                        var objClass = ReflectionType as Raw.ReflectionClass;
                        _CachedDisplayValue = "NULL of " + objClass.Name;
                    }
                    else
                    {
                        Guid objectId = Guid.Empty;
                        if (!Raw.Utils.ParseGuid( _CachedValue, ref objectId))
                        {
                            _CachedDisplayValue = "<invalid guid>";
                        }
                        else
                        {
                            var obj = Object.RootDataCollection.Find(objectId);
                            if (obj == null)
                            {
                                _CachedDisplayValue = "<unknown object '" + _CachedValue + "'>";
                            }
                            else
                            {
                                _CachedDisplayObject = obj;
                                _CachedDisplayValue = Raw.Utils.GetObjectNameSafe( Object.RootReflection, obj );
                            }
                        }
                    }
                }
                else
                {
                    // cache the actual value
                    _CachedValue = singleVal.Value.Value;
                    _CachedDisplayValue = _CachedValue;
                }
            }
        }
        
        /// <summary>
        /// Data consistency token - checked every time to ensure that our displayed values are OK
        /// </summary>
        private Red.Core.DataConsistency.StateToken _ConsistencyToken;

        /// <summary>
        /// String representing value of this property, it's cached for performance reasons
        /// This string is empty for arrays as they don't have a value directly (only the elements have the value)
        /// Consistency of this cached value with the original data is tracked via _ConsistencyToken
        /// </summary>        
        private string _CachedValue;

        /// <summary>
        /// String to display as the value of this property, it's cached for performance reasons
        /// This is NOT the actual value but just a user-displayable version of it
        /// Consistency of this cached value with the original data is tracked via _ConsistencyToken
        /// </summary>
        private string _CachedDisplayValue;

        /// <summary>
        /// In case the type of the property is an object reference this is the cached pointer to that object
        /// Again, this is done only because of performance considerations
        /// Consistency of this cached value with the original data is tracked via _ConsistencyToken
        /// </summary>
        private Raw.DataObject _CachedDisplayObject;

        /// <summary>
        /// Is this property writable ?
        /// Cached due to performance considerations.
        /// Consistency of this cached value with the original data is tracked via _ConsistencyToken
        /// </summary>
        private Boolean _CachedIsWritable;

        /// <summary>
        /// Is this a property with no value?
        /// Cached due to performance considerations.
        /// Consistency of this cached value with the original data is tracked via _ConsistencyToken
        /// </summary>
        private Boolean _CachedNoValue;

        /// <summary>
        /// Cached value - is this value overridden
        /// </summary>
        private Boolean _CachedIsOverriden;

        /// <summary>
        /// Cached value - is this value suppressed
        /// </summary>
        private Boolean _CachedIsSuppressed;

        /// <summary>
        /// Cached value - have we defaulted to the default value of this property ?
        /// </summary>
        private Boolean _CachedIsDefaultValue;

        /// <summary>
        /// Number of array elements in the array
        /// Cached due to performance considerations.
        /// Consistency of this cached value with the original data is tracked via _ConsistencyToken
        /// </summary>
        private UInt32 _CachedNumArrayElements;

        /// <summary>
        /// Cached ValueIDs of all valid array elements
        /// Cached due to performance considerations.
        /// Consistency of this cached value with the original data is tracked via _ConsistencyToken
        /// </summary>
        private Guid[] _CachedArrayElementGUIDs;

        /// <summary>
        /// Internal array "generation ID" tracking more complicated changes
        /// </summary>
        private UInt64 _CachedCompoundGenerationId;

        /// <summary>
        /// String representing value of this property
        /// 
        /// This attribute accesses the cached value.
        /// The value will be automatically refreshed if the consistency of data requires that
        /// </summary>
        public string CachedValue
        {
            get
            {
                EnsureDataConsistency();
                return _CachedValue;
            }
        }

        /// <summary>
        /// String to display as the value of this property, it's cached for performance reasons
        /// This is NOT the actual value but just a user-displayable version of it
        /// 
        /// This attribute accesses the cached value.
        /// The value will be automatically refreshed if the consistency of data requires that
        /// </summary>
        public string CachedDisplayValue
        {
            get
            {
                EnsureDataConsistency();
                return _CachedDisplayValue;
            }
        }

        /// <summary>
        /// In case the type of the property is an object reference this is the cached pointer to that object
        /// </summary>
        /// 
        /// This attribute accesses the cached value.
        /// The value will be automatically refreshed if the consistency of data requires that
        /// </summary>
        public Raw.DataObject CachedDisplayObject
        {
            get
            {
                EnsureDataConsistency();
                return _CachedDisplayObject;
            }
        }

        /// <summary>
        /// Cached value - is this value overridden
        /// 
        /// This attribute accesses the cached value.
        /// The value will be automatically refreshed if the consistency of data requires that
        /// </summary>
        public Boolean CachedIsOverriden
        {
            get
            {
                EnsureDataConsistency();
                return _CachedIsOverriden;
            }
        }

        /// <summary>
        /// Cached value - is this value suppressed
        /// 
        /// This attribute accesses the cached value.
        /// The value will be automatically refreshed if the consistency of data requires that
        /// </summary>
        public Boolean CachedIsSuppressed
        {
            get
            {
                EnsureDataConsistency();
                return _CachedIsSuppressed;
            }
        }

        /// <summary>
        /// Cached value - is this a property with no value ?
        /// 
        /// This attribute accesses the cached value.
        /// The value will be automatically refreshed if the consistency of data requires that
        /// </summary>
        public Boolean CachedNoValue
        {
            get
            {
                EnsureDataConsistency();
                return _CachedNoValue;
            }
        }

        /// <summary>
        /// Cached value - can we write into this property?
        /// 
        /// This attribute accesses the cached value.
        /// The value will be automatically refreshed if the consistency of data requires that
        /// </summary>
        public Boolean CachedIsWritable
        {
            get
            {
                EnsureDataConsistency();
                return _CachedIsWritable;
            }
        }

        /// <summary>
        /// Cached value - have we defaulted to the default value for this property ?
        /// 
        /// This attribute accesses the cached value.
        /// The value will be automatically refreshed if the consistency of data requires that
        /// </summary>
        public Boolean CachedIsDefaultValue
        {
            get
            {
                EnsureDataConsistency();
                return _CachedIsDefaultValue;
            }
        }        

        /// <summary>
        /// Cached value - number of elements - arrays only
        /// 
        /// This attribute accesses the cached value.
        /// The value will be automatically refreshed if the consistency of data requires that
        /// </summary>
        public UInt32 CachedNumArrayElements
        {
            get
            {
                EnsureDataConsistency();
                return _CachedNumArrayElements;
            }
        }

        /// <summary>
        /// Cached array element GUIDs - arrays only
        /// This will return a full composition of the array
        /// 
        /// This attribute accesses the cached value.
        /// The value will be automatically refreshed if the consistency of data requires that
        /// </summary>
        public Guid[] CachedArrayElementGUIDs
        {
            get
            {
                EnsureDataConsistency();
                return _CachedArrayElementGUIDs;
            }
        }

        /// <summary>
        /// Cached array consistency "generation ID"
        /// Used to track changes that could not be tracked otherwise - like swapping elements
        /// </summary>
        public UInt64 CachedCompoundGenerationId
        {
            get
            {
                EnsureDataConsistency();
                return _CachedCompoundGenerationId;
            }
        }

    }
}
